/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Simple settings class implementation.
 * \author Antti Maula <antti.maula@tkk.fi>
 */
#include "settings.hpp"

#include <stdio.h>
#include <time.h>
#include <assert.h>
#include <algorithm>

#include "owndebug.h"

#ifdef COMPILER_CODEBLOCKS
#define xmlFree(x) free(x)
#endif

//******************************************************************************
//******************************************************************************
//******************************************************************************
using namespace gim;
//******************************************************************************
//******************************************************************************
//******************************************************************************

CSettings::CSettings()
  : iSettingsDoc(NULL),
    iSettingsFilename(),
    iKeyValueArray(),
    iBaseKey(),
    iSettingsMutex(ownMutex_Init()),
    iLastSearchedKey(),
    iLastSearchedKeyIterator(iKeyValueArray.end())

{
  assert(iSettingsMutex != NULL);
  dPrint(10,"Constructed new CSettings @%p", this);
}
//******************************************************************************

CSettings::~CSettings()
{
  dPrint(10,"Destroying CSettings @%p", this);
  if (iSettingsDoc) xmlFreeDoc(iSettingsDoc);
  ownMutex_Destroy(iSettingsMutex);
}
//******************************************************************************

bool CSettings::GetValue(const std::string &key, int &data) const
{
  UpdateIterator(key);
  
  if (iLastSearchedKeyIterator ==  iKeyValueArray.end()) return false;
  if (sscanf(iLastSearchedKeyIterator->second.data.c_str(),"%d", &data) != 1) return false;

  return true;
}
//******************************************************************************

bool CSettings::GetValue(const std::string &key, unsigned int &data) const
{
  UpdateIterator(key);

  if (iLastSearchedKeyIterator ==  iKeyValueArray.end()) return false;
  if (sscanf(iLastSearchedKeyIterator->second.data.c_str(),"%u", &data) != 1) return false;

  return true;
}
//******************************************************************************

bool CSettings::GetValue(const std::string &key, std::string &data) const
{
  UpdateIterator(key);

  if (iLastSearchedKeyIterator ==  iKeyValueArray.end()) return false;
  data = iLastSearchedKeyIterator->second.data;

  return true;
}
//******************************************************************************

bool CSettings::GetValue(const std::string &key, double &data) const
{
  UpdateIterator(key);

  if (iLastSearchedKeyIterator ==  iKeyValueArray.end()) return false;
  if (sscanf(iLastSearchedKeyIterator->second.data.c_str(),"%lf", &data) != 1) return false;

  return true;
}
//******************************************************************************

bool CSettings::SetValue(const std::string &key, int data, bool replace)
{
  char dataV[64];
  sprintf(dataV, "%d", data);
  TKeyValue kv(key, dataV);
  
  if (replace) {
    std::multimap<std::string, TKeyValue>::iterator i =
      iKeyValueArray.find (iBaseKey + key);
    if (i != iKeyValueArray.end()) iKeyValueArray.erase(i);
    
  }
  iKeyValueArray.insert(std::pair<std::string, TKeyValue>(key, kv));

  return true;
}
//******************************************************************************

bool CSettings::SetValue(const std::string &key, const unsigned int data, bool replace)
{
  char dataV[64];
  sprintf(dataV, "%u", data);
  TKeyValue kv(key, dataV);
  
  if (replace) {
    std::multimap<std::string, TKeyValue>::iterator i =
      iKeyValueArray.find (iBaseKey + key);
    if (i != iKeyValueArray.end()) iKeyValueArray.erase(i);
    
  }
  iKeyValueArray.insert(std::pair<std::string, TKeyValue>(key, kv));

  return true;
}
//******************************************************************************

bool CSettings::SetValue(const std::string &key, const std::string &data, bool replace)
{
  TKeyValue kv(key, data);

  if (replace) {
    std::multimap<std::string, TKeyValue>::iterator i =
      iKeyValueArray.find (iBaseKey + key);
    if (i != iKeyValueArray.end()) iKeyValueArray.erase(i);
    
  }
  iKeyValueArray.insert(std::pair<std::string, TKeyValue>(key, kv));

  return true;
}
//******************************************************************************

bool CSettings::SetValue(const std::string &key, double data, bool replace)
{
  char dataV[64];
  sprintf(dataV, "%lf", data);
  TKeyValue kv(key, dataV);

  if (replace) {
    std::multimap<std::string, TKeyValue>::iterator i =
      iKeyValueArray.find (iBaseKey + key);
    if (i != iKeyValueArray.end()) iKeyValueArray.erase(i);
    
  }
  iKeyValueArray.insert(std::pair<std::string, TKeyValue>(key, kv));

  return true;
}
//******************************************************************************

bool CSettings::SetBaseKey(const std::string &aBaseKey)
{
  bool result = true;
  iBaseKey = aBaseKey;

  // If the basekey doesn't end in Dot, append it.
  if (iBaseKey.size() > 0 && iBaseKey[iBaseKey.size()-1] != '.') iBaseKey += ".";

  dPrint(4,"Using '%s' as BaseKey", iBaseKey.c_str());
  return result;
}
//******************************************************************************

std::string CSettings::GetBaseKey(void) const
{
  return iBaseKey;
}
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************

void CSettings::UpdateIterator(const std::string &key) const
{
  // Got key?
  if (key.size()) {
    // Yes, do new search.
    iLastSearchedKey = iBaseKey + key;
    iLastSearchedKeyIterator = iKeyValueArray.find (iLastSearchedKey);
    
  } else {

    // No, try checking the next key. (Verify that the iter is not at end.
    if (iLastSearchedKeyIterator != iKeyValueArray.end()) {
      ++iLastSearchedKeyIterator;
      
      if (iLastSearchedKeyIterator != iKeyValueArray.end()) {
        // Check whether the next key in map matches the current. If NOT, return false.
        if (iLastSearchedKeyIterator->first != iLastSearchedKey) {
          
          // Mark the iterator at end (marking the operation as failed)
          iLastSearchedKeyIterator = iKeyValueArray.end();
          
        }
      }
    }
  }
}
//******************************************************************************

bool CSettings::parseValue(const std::string &group, xmlNodePtr valuenode)
{
  assert(valuenode != NULL);

  std::string finalKey(group); // Init with group
  xmlChar *key, *data;
  key = xmlGetProp(valuenode, (xmlChar *) "key");
  data = xmlGetProp(valuenode, (xmlChar *) "data");
  if (key && data) {
    finalKey.append((const char *)key);
    dPrint(10, "Adding key: '%s' '%s'",
    	   finalKey.c_str(), data);
    TKeyValue kv(finalKey, (const char *)data);
    iKeyValueArray.insert(std::pair<std::string, TKeyValue>(kv.key, kv));
  }

  if (key) xmlFree(key);
  if (data) xmlFree(data);
  return true;
}
//******************************************************************************

bool CSettings::parseGroup(const std::string &group, xmlNodePtr groupnode)
{
  std::string currentGroup(group);
  xmlChar *groupName;
  bool retVal = true;

  if (groupnode == NULL) return false;
  if (groupnode->type != XML_ELEMENT_NODE) return false;

  //  define the currentGroup variable
  groupName = xmlGetProp(groupnode, (xmlChar *) "name");
  if (groupName && groupName) {
    currentGroup.append((const char *)groupName); // Add this groups name
    currentGroup.append("."); // Separator for next one
    //    dPrint(4, "Got groupName: '%s', whole group is now: '%s'",
    //	   groupName, currentGroup.c_str());
  }
  if (groupName) xmlFree(groupName);

  // Go in (knock knock)
  groupnode = groupnode->children;

  while(groupnode != NULL && retVal == true) {
    if (groupnode->type == XML_ELEMENT_NODE) {
      if( !xmlStrcmp(groupnode->name, (xmlChar *) "group") ) {
        retVal = parseGroup(currentGroup, groupnode);
      } else if ( !xmlStrcmp(groupnode->name, (xmlChar *) "value") ) {
        retVal = parseValue(currentGroup, groupnode);
      }
    }
    groupnode = groupnode->next;
  }
  return retVal;
}
//******************************************************************************

bool CSettings::parseSet(xmlNodePtr setnode)
{
  bool result = true;
  assert(setnode != NULL);
  xmlNodePtr node = setnode;
  
  TKeyValue kv;

  // Process nodes.
  while(node != NULL && 
        result) {
    
    // Check that node is ELEMENT_NODE otherwise skip.
    if (node->type == XML_ELEMENT_NODE) {
      if( !xmlStrcmp(node->name, (xmlChar *) "key") ) {
        // Got key.
        xmlChar *content = xmlNodeListGetString(iSettingsDoc, node->xmlChildrenNode, 0);
        if (content) {
          kv.key = (char*)content;
          dPrint(ODDEBUG(2),"Got 'key': '%s'", content);
          xmlFree(content);
        }
        
      } else if( !xmlStrcmp(node->name, (xmlChar *) "value") ) {
        // Got value.
        xmlChar *content = xmlNodeListGetString(iSettingsDoc, node->xmlChildrenNode, 0);
        if (content) {
          kv.data = (char*)content;
          dPrint(ODDEBUG(2),"Got 'value': '%s'", content);
          xmlFree(content);
        }
        
      } else if( !xmlStrcmp(node->name, (xmlChar *) "description") ) {
        // Got description.
        xmlChar *content = xmlNodeListGetString(iSettingsDoc, node->xmlChildrenNode, 0);
        if (content) {
          kv.description = (char*)content;
          dPrint(ODDEBUG(2),"Got 'description': '%s'", content);
          xmlFree(content);
        }
        
      } else {
        // Got something else?
        dPrint(ODVINFO,"Got unknown node '%s' - will skip", node->name);

      }
    }
    node = node->next;
  }

  // Finally, check which fields were read.
  if (kv.key.size() && kv.data.size()) {
    dPrint(ODDEBUG(1),"Succesfully read a SET. Adding to container");
    iKeyValueArray.insert(std::pair<std::string, TKeyValue>(kv.key, kv));
    
  } else if (!kv.key.size() && !kv.data.size()) {
    dPrint(ODWARN,"SET element doesn't contain any of the required elements");
    result = false;

  }
  
  return result;
}
//******************************************************************************

bool CSettings::decodeGroupFormat(xmlNodePtr groupnode)
{
  bool result = true;
 
  // Just init by calling the parseGroup parser.
  if (parseGroup("", groupnode) == false) {
    dPrint(1, "Error parsing settings!");
    result = false;
  }

  return result;
}
//******************************************************************************

bool CSettings::decodeSettingsFormat(xmlNodePtr rootnode)
{
  bool result = true;
  xmlNodePtr node = rootnode;
  
  if (rootnode == NULL) return false;
  if (rootnode->type != XML_ELEMENT_NODE) return false;

  // Go in (knock knock)
  node = node->children;

  // Process nodes.
  while(node != NULL && result) {
    
    // Check that node is ELEMENT_NODE otherwise skip.
    if (node->type == XML_ELEMENT_NODE) {
      if( !xmlStrcmp(node->name, (xmlChar *) "set") ) {
        // Identified as 'set'. Proceed with parse.
        result = parseSet(node->children);
      }
    }
    node = node->next;
  }

  // If still succesfull, set success value by keyvaluearray size.
  if (result) result = iKeyValueArray.size() > 0;

  return result;
}
//******************************************************************************

bool CSettings::ReadSettingsXMLFile(const std::string &filename)
{
  xmlNodePtr node;
  bool retVal = true;
  
  // If pre-existing doc, free it
  if (iSettingsDoc) xmlFreeDoc(iSettingsDoc);

  /// Open the config file
  iSettingsDoc = xmlReadFile(filename.c_str(), NULL, 0);
  if(iSettingsDoc == NULL){
    dPrint(ODERROR, "Failed to open settings file '%s'!", filename.c_str());
    return false;
  }
  
  // Get root element.
  node = xmlDocGetRootElement(iSettingsDoc);		// Retrieve the root element

  // Attempt to decode. First try with new Settings format, if fails,
  // try older Group format
  retVal =  decodeSettingsFormat(node);
  if (!retVal) retVal = decodeGroupFormat(node);

  // Now, free resources, as they are not required anymore.
  iSettingsFilename = filename;
  xmlFreeDoc(iSettingsDoc);
  iSettingsDoc = NULL;
  
  if (retVal) {
    dPrint(ODVINFO,"Succesfully loaded settings from '%s' - resulted in %d keys/values",
           filename.c_str(), iKeyValueArray.size());
    
  } else {
    dPrint(ODINFO,"Failed to load settings from file '%s'!",
           filename.c_str());
  
  }

  return retVal;
}
//******************************************************************************

bool CSettings::WriteSettingsXMLFile(const std::string &filename)
{
  bool result = false;
  const std::string &fname = filename.size() ? filename : iSettingsFilename;

  FILE *f = fopen(fname.c_str(), "w+");
  if (f) {
    fprintf(f, "<?xml version=\"1.0\"?>\n");
    fprintf(f, "<!-- Generated by CSettings $Revision: 1.13 $ -->\n");
    fprintf(f, "<settings version=\"1.0\">\n");
    for(EACH_IN_i(iKeyValueArray)) {
      fprintf(f, " <set>\n");
      fprintf(f, "  <description>%s</description>\n", i->second.description.c_str());
      fprintf(f, "  <key>%s</key>\n", i->second.key.c_str());
      fprintf(f, "  <value>%s</value>\n", i->second.data.c_str());
      fprintf(f, " </set>\n");
    }
    fprintf(f, "</settings>\n");

    fclose(f);
            
    result = true;
  }

  return result;
}

//******************************************************************************

bool CSettings::ReadSettingsXMLString(const std::string xmldata)
{
  xmlNodePtr node;
  bool retVal = true;
  
  // If pre-existing doc, free it
  if (iSettingsDoc) xmlFreeDoc(iSettingsDoc);
  
  /// Open the config file
  iSettingsDoc = xmlReadMemory(xmldata.c_str(), xmldata.size() ,NULL, NULL, 0);
  if(iSettingsDoc == NULL){
    dPrint(ODERROR, "Failed to open settings string!");
    return false;
  }
  
  // Get root element.
  node = xmlDocGetRootElement(iSettingsDoc);		// Retrieve the root element
  
  // Attempt to decode. First try with new Settings format, if fails,
  // try older Group format
  retVal =  decodeSettingsFormat(node);
  if (!retVal) retVal = decodeGroupFormat(node);
  
  // Now, free resources, as they are not required anymore.
  xmlFreeDoc(iSettingsDoc);
  iSettingsDoc = NULL;
  
  if (retVal) {
    dPrint(ODVINFO,"Succesfully loaded settings string - resulted in %d keys/values",
           iKeyValueArray.size());
    
  } else {
    dPrint(ODINFO,"Failed to load settings from string!");
    
  }
  
  return retVal;
}
//******************************************************************************

bool CSettings::WriteSettingsXMLString(std::string &strng)
{
  bool result = false;
  std::stringstream tempstringstream;
  
  tempstringstream << "<?xml version=\"1.0\"?>\n"
                   << "<!-- Generated by CSettings $Revision: 1.13 $ -->\n"
                   << "<settings version=\"1.0\">\n";
  
  for(EACH_IN_i(iKeyValueArray)) {
    tempstringstream << " <set>\n"
                     << "  <description>"
                     << i->second.description.c_str()
                     << "</description>\n"
                     << "  <key>"
                     << i->second.key.c_str()
                     << "</key>\n"
                     << "  <value>"
                     << i->second.data.c_str()
                     << "</value>\n"
                     << " </set>\n";
  }
  tempstringstream << "</settings>\n";           
  result = true;
  strng = tempstringstream.str();

  return result;
}
//******************************************************************************
//******************************************************************************
//******************************************************************************
